In [ ]:
%matplotlib inline
from math import *
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from QuantLib import *
from PyFin.Math.Distributions import CumulativeNormalDistribution

plt.style.use('fivethirtyeight')

1. Functions



In [ ]:
def bs_theoretical_price(payoff, spot, ttm, volatility, rf_rate, finance_rate=None):
    
    if not finance_rate:
        finance_rate = rf_rate
    
    forward = spot * exp(finance_rate * ttm)
    std_dev = volatility * sqrt(ttm)
    discount = exp(-rf_rate * ttm)
    return blackFormula(payoff.optionType(), payoff.strike(), forward, std_dev, discount)

In [ ]:
_dist = CumulativeNormalDistribution()

def _exercise(payoff, spot):
    return payoff(spot)

_exercise = np.frompyfunc(_exercise, 2, 1)


def _create_path(rsg, ln_spot, drift, diffusion, delta_t):
    rnd = np.array(rsg.nextSequence().value())
    in_c = delta_t * drift + np.sqrt(delta_t) * diffusion * rnd
    inc_c_cum = np.cumsum(in_c)
    return np.exp(np.concatenate(([ln_spot], ln_spot + inc_c_cum)))


def _bs_delta(option_type, finance_rate, volatility, ttm, spot, strike):
    money_ness = log(spot / strike)
    drift = (finance_rate + 0.5 * (volatility ** 2)) * ttm
    d1 = (money_ness + drift) / volatility / sqrt(ttm)
    call_delta =  _dist(d1)
    
    if option_type == Option.Call:
        return call_delta
    elif option_type == Option.Put:
        return call_delta - 1.

_bs_delta = np.frompyfunc(_bs_delta, 6, 1)


def _hedging_on_path(payoff, ttm, time_grids, spot_path, volatility, inflations, rf_rate, finance_rate, trading_cost):
    delta_t = time_grids[0] - time_grids[1]
    deltas = _bs_delta(payoff.optionType(), finance_rate, volatility, time_grids, spot_path[:-1], payoff.strike())
    borrows = spot_path[:-1] * deltas
    finance_cost = borrows * finance_rate * delta_t
    stock_pnl = deltas * (spot_path[1:] - spot_path[:-1])
    trading_slipge = np.abs(np.concatenate(([deltas[0]], np.diff(deltas)))) * spot_path[:-1] * trading_cost
    hedging_pnl = ((stock_pnl - finance_cost - trading_slipge) * inflations).sum()
    
    exercise_pnl = payoff(spot_path[-1])
    total_cost = hedging_pnl - exercise_pnl
    
    return exp(-rf_rate * ttm) * total_cost


class HedgeAnalysor(object):
    
    def __init__(self,
                 payoff,
                 trading_cost=0.):
        self.payoff = payoff
        self.trading_cost = trading_cost
    
    @staticmethod
    def _prepare_parameters(rf_rate, finance_rate, underlying_risk_return):
        if finance_rate is None:
            finance_rate = rf_rate
        
        if underlying_risk_return is None:
            underlying_risk_return = rf_rate
        
        return finance_rate, underlying_risk_return

    def exercise(self, spots):
        return _exercise(self.payoff, spots)
    
    def _hedge_path(self, rsg, ttm, time_grids, ln_spot, volatility, drift, diffusion, delta_t, inflations, rf_rate, finance_rate):
        spot_path = _create_path(rsg, ln_spot, drift, diffusion, delta_t)
        return _hedging_on_path(self.payoff, ttm, time_grids, spot_path, volatility, inflations, rf_rate, finance_rate, self.trading_cost)
        
    
    def hedge_cost(self,
                   rf_rate,
                   volatility,
                   realized_vol,
                   ttm,
                   finance_rate=None,
                   underlying_risk_return=None,
                   spot=1.,
                   time_steps=50,
                   simulations=100,
                   seed=20):
        rng = MersenneTwisterUniformRng(seed)
        rsg = MersenneTwisterUniformRsg(dimensionality=time_steps,
                                        rng=rng)
        rsg = InvCumulativeMersenneTwisterGaussianRsg(rsg)
        
        finance_rate, underlying_risk_return = self._prepare_parameters(rf_rate,
                                                                        finance_rate,
                                                                        underlying_risk_return)
        print("risk free: {0:.02f}%".format(rf_rate*100))
        print('finance rate: {0:.02f}%'.format(finance_rate*100))
        print('underlying risk return: {0:.02f}%'.format(underlying_risk_return*100))
        print('implied vol: {0:.02f}%'.format(volatility))
        print('realized vol: {0:.02f}%'.format(realized_vol))
        print('number of simulations: {0}'.format(simulations))
        print('time to maturity: {0} yrs.'.format(ttm))
        print('time steps: {0}'.format(time_steps))
        print('trading cost: {0:0.4f}%'.format(self.trading_cost*100))
        print('payoff: type={0}, k={1}'.format(self.payoff.optionType(), self.payoff.strike()))
        
        ln_spot = log(spot)
        delta_t = ttm / time_steps

        drift = underlying_risk_return - 0.5 * (realized_vol ** 2)
        diffusion = realized_vol
        time_grids = np.linspace(ttm, 0, num=time_steps, endpoint=False)
        inflations = np.exp(finance_rate * (time_grids - delta_t))
        
        hedging_cost_batch = np.zeros(simulations)
        
        for i in range(simulations):
            hedging_cost = self._hedge_path(rsg,
                                            ttm,
                                            time_grids,
                                            ln_spot,
                                            volatility,
                                            drift,
                                            diffusion,
                                            delta_t,
                                            inflations,
                                            rf_rate,
                                            finance_rate)
            hedging_cost_batch[i] = hedging_cost
        
        return -hedging_cost_batch

In [ ]:
rf_rate = 0.04
finance_rate = 0.06
underlying_risk_return = -0.15
strike = 1.
volatility = 0.30
realized_vol = 0.30
ttm = 0.25
spot = 1.
trading_cost = 0.0
simulations = 50000
time_steps = int(ttm * 250)

payoff = PlainVanillaPayoff(Option.Call, strike)
hf = HedgeAnalysor(payoff, trading_cost)

In [ ]:
%%time

res = hf.hedge_cost(rf_rate=rf_rate,
                    volatility=volatility,
                    realized_vol=realized_vol,
                    ttm=ttm,
                    spot=spot,
                    finance_rate=finance_rate,
                    underlying_risk_return=underlying_risk_return,
                    simulations=simulations,
                    time_steps=time_steps)
bs_price = bs_theoretical_price(payoff, spot, ttm, volatility, rf_rate, finance_rate)

In [ ]:
fig, axes = plt.subplots(1, 1, figsize=(12, 6), sharex=True)
axes = [axes]

for i, ax in enumerate(axes):
    ax.hist(res, bins=50)
    ax.axvline(x=bs_price, color='red', linestyle='dashed', label='Theoretical: {0:.02f}%'.format(bs_price*100))
    ax.axvline(x=res.mean(), color='green', linestyle='dashed', label='Hedging Mean')
    ax.axvline(x=np.percentile(res, 1), color='yellow', linestyle='dashed', label='Hedging per. 1%')
    ax.axvline(x=np.percentile(res, 5), color='black', linestyle='dashed', label='Hedging per. 5%')
    ax.axvline(x=np.percentile(res, 95), color='black', linestyle='dashed', label='Hedging per. 95%')
    ax.axvline(x=np.percentile(res, 99), color='yellow', linestyle='dashed', label='Hedging per. 99%')
    ax.set_title("Hedging v.s. Theoretical (tc = {0}%, r_vol = {1:0.2f}%, k={2})".format(trading_cost*100,
                                                                                                    realized_vol*100,
                                                                                                strike))
    ax.legend()

In [ ]:
# produce the table
index = ['理论', '对冲(平均)', '对冲(分位数 1%)', '对冲(分位数 5%)', '对冲(分位数 95%)', '对冲(分位数 99%)']
values = np.array([bs_price, res.mean(), np.percentile(res, 1), np.percentile(res, 5), np.percentile(res, 95), np.percentile(res, 99)])
rel_values = values / values[0] - 1.
df = pd.DataFrame(data={'成本': values, '相对': rel_values}, index=index)
df

2. Base Parameters



In [ ]:
rf_rate = rf_rate
finance_rate = finance_rate
underlying_risk_return = underlying_risk_return
strike = 1.
volatility = 0.30
realized_vol = 0.30
ttm = 0.25
spot = 1.
trading_cost = 0.0015
simulations = 50000
time_steps = int(ttm * 250)

payoff = PlainVanillaPayoff(Option.Call, strike)
hf = HedgeAnalysor(payoff, trading_cost=trading_cost)

3. Scenario Analysis


2.1 Strike Scenarios



In [ ]:
strike_scenarios =[0.9, 1.0, 1.1]

In [ ]:
simulations_res = []
bs_prices_res = []

for i, this_strike in enumerate(strike_scenarios):
    print("\nScenarios {0} ......".format(i+1))
    this_payoff = PlainVanillaPayoff(Option.Call, this_strike)
    this_bs_price = bs_theoretical_price(this_payoff, spot, ttm, volatility, rf_rate, finance_rate)
    hf = HedgeAnalysor(this_payoff, trading_cost)
    path_res = hf.hedge_cost(rf_rate=rf_rate,
                             volatility=volatility,
                             realized_vol=realized_vol,
                             time_steps=time_steps,
                             ttm=ttm,
                             finance_rate=finance_rate,
                             underlying_risk_return=underlying_risk_return,
                             spot=spot,
                             simulations=simulations)
    
    simulations_res.append(path_res)
    bs_prices_res.append(this_bs_price)

In [ ]:
fig, axes = plt.subplots(len(strike_scenarios), 1, figsize=(12, 6 * len(strike_scenarios)), sharex=True)

if not hasattr(axes, '__iter__'):
    axes = [axes]

for i, ax in enumerate(axes):
    res = simulations_res[i]
    ax.hist(res, bins=50)
    ax.axvline(x=bs_prices_res[i], color='red', linestyle='dashed', label='Theoretical: {0:.02f}%'.format(bs_prices_res[i]))
    ax.axvline(x=res.mean(), color='green', linestyle='dashed', label='Hedging Mean')
    ax.axvline(x=np.percentile(res, 1), color='yellow', linestyle='dashed', label='Hedging per. 1%')
    ax.axvline(x=np.percentile(res, 5), color='black', linestyle='dashed', label='Hedging per. 5%')
    ax.axvline(x=np.percentile(res, 95), color='black', linestyle='dashed', label='Hedging per. 95%')
    ax.axvline(x=np.percentile(res, 99), color='yellow', linestyle='dashed', label='Hedging per. 99%')
    ax.set_title("Hedging v.s. Theoretical (tc = {0:.2f}%, r_vol = {1:0.2f}%, k={2})".format(trading_cost*100,
                                                                                                    realized_vol*100,
                                                                                                       strike_scenarios[i]))
    ax.legend()

In [ ]:
# produce the table
index = ['理论', '对冲(平均)', '对冲(分位数 1%)', '对冲(分位数 5%)', '对冲(分位数 95%)', '对冲(分位数 99%)']
col_names = ['{0:.0f}%'.format(t*100) for t in strike_scenarios]

values = np.zeros((len(index), len(col_names)))
for j, res in enumerate(simulations_res):
    this_values = np.array([bs_prices_res[j], res.mean(), np.percentile(res, 1), np.percentile(res, 5), np.percentile(res, 95), np.percentile(res, 99)])
    values[:, j] = this_values
    
df = pd.DataFrame(data=values, columns=col_names, index=index)
df

2.2 Trading Cost Scenarios



In [ ]:
trading_cost_scenarios =[0.0010, 0.0015, 0.0020]

In [ ]:
simulations_res = []
bs_price = bs_theoretical_price(payoff, spot, ttm, volatility, rf_rate, finance_rate)

for i, this_trading_cost in enumerate(trading_cost_scenarios):
    print("\nScenarios {0} ......".format(i+1))
    hf = HedgeAnalysor(payoff, this_trading_cost)
    path_res = hf.hedge_cost(rf_rate=rf_rate,
                             volatility=volatility,
                             realized_vol=realized_vol,
                             time_steps=time_steps,
                             ttm=ttm,
                             finance_rate=finance_rate,
                             underlying_risk_return=underlying_risk_return,
                             spot=spot,
                             simulations=simulations)
    
    simulations_res.append(path_res)

In [ ]:
fig, axes = plt.subplots(len(trading_cost_scenarios), 1, figsize=(12, 6 * len(trading_cost_scenarios)), sharex=True)

if not hasattr(axes, '__iter__'):
    axes = [axes]

for i, ax in enumerate(axes):
    res = simulations_res[i]
    ax.hist(res, bins=50)
    ax.axvline(x=bs_price, color='red', linestyle='dashed', label='Theoretical: {0:.02f}%'.format(bs_price*100))
    ax.axvline(x=res.mean(), color='green', linestyle='dashed', label='Hedging Mean')
    ax.axvline(x=np.percentile(res, 1), color='yellow', linestyle='dashed', label='Hedging per. 1%')
    ax.axvline(x=np.percentile(res, 5), color='black', linestyle='dashed', label='Hedging per. 5%')
    ax.axvline(x=np.percentile(res, 95), color='black', linestyle='dashed', label='Hedging per. 95%')
    ax.axvline(x=np.percentile(res, 99), color='yellow', linestyle='dashed', label='Hedging per. 99%')
    ax.set_title("Hedging v.s. Theoretical (tc = {0:.2f}%, r_vol = {1:0.2f}%, k={2})".format(trading_cost_scenarios[i]*100,
                                                                                                    realized_vol*100,
                                                                                                       strike))
    ax.legend()

In [ ]:
# produce the table
index = ['理论', '对冲(平均)', '对冲(分位数 1%)', '对冲(分位数 5%)', '对冲(分位数 95%)', '对冲(分位数 99%)']
col_names = ['{0:.2f}%'.format(t*100) for t in trading_cost_scenarios]

values = np.zeros((len(index), len(col_names)))
for j, res in enumerate(simulations_res):
    this_values = np.array([bs_price, res.mean(), np.percentile(res, 1), np.percentile(res, 5), np.percentile(res, 95), np.percentile(res, 99)])
    values[:, j] = this_values
    
df = pd.DataFrame(data=values, columns=col_names, index=index)
df

2.3 Volatility Scenario



In [ ]:
volatility_scenarios = [0.20, 0.30, 0.40]

In [ ]:
simulations_res = []
bs_price = bs_theoretical_price(payoff, spot, ttm, volatility, rf_rate, finance_rate)
hf = HedgeAnalysor(payoff, trading_cost)

for i, this_volatility in enumerate(volatility_scenarios):
    print("\nScenarios {0} ......".format(i+1))
    path_res = hf.hedge_cost(rf_rate=rf_rate,
                             volatility=volatility,
                             realized_vol=this_volatility,
                             time_steps=time_steps,
                             ttm=ttm,
                             finance_rate=finance_rate,
                             underlying_risk_return=underlying_risk_return,
                             spot=spot,
                             simulations=simulations)
    
    simulations_res.append(path_res)

In [ ]:
fig, axes = plt.subplots(len(volatility_scenarios), 1, figsize=(12, 6 * len(volatility_scenarios)), sharex=True)

if not hasattr(axes, '__iter__'):
    axes = [axes]

for i, ax in enumerate(axes):
    res = simulations_res[i]
    ax.hist(res, bins=50)
    ax.axvline(x=bs_price, color='red', linestyle='dashed', label='Theoretica: {0:.02f}%'.format(bs_price*100))
    ax.axvline(x=res.mean(), color='green', linestyle='dashed', label='Hedging Mean')
    ax.axvline(x=np.percentile(res, 1), color='yellow', linestyle='dashed', label='Hedging per. 1%')
    ax.axvline(x=np.percentile(res, 5), color='black', linestyle='dashed', label='Hedging per. 5%')
    ax.axvline(x=np.percentile(res, 95), color='black', linestyle='dashed', label='Hedging per. 95%')
    ax.axvline(x=np.percentile(res, 99), color='yellow', linestyle='dashed', label='Hedging per. 99%')
    ax.set_title("Hedging v.s. Theoretical (tc = {0:.2f}%, r_vol = {1:0.2f}%, k={2})".format(trading_cost*100,
                                                                                                    volatility_scenarios[i]*100,
                                                                                                       strike))
    ax.legend()

In [ ]:
# produce the table
index = ['理论', '对冲(平均)', '对冲(分位数 1%)', '对冲(分位数 5%)', '对冲(分位数 95%)', '对冲(分位数 99%)']
col_names = ['{0:.2f}%'.format(v*100) for v in volatility_scenarios]

values = np.zeros((len(index), len(col_names)))
for j, res in enumerate(simulations_res):
    this_values = np.array([bs_price, res.mean(), np.percentile(res, 1), np.percentile(res, 5), np.percentile(res, 95), np.percentile(res, 99)])
    values[:, j] = this_values
    
df = pd.DataFrame(data=values, columns=col_names, index=index)
df

In [ ]:


In [ ]: